package com.gitee.wsl.mathematics.algebraic


/**
 * Represents an algebraic structure.
 *
 * @param T the type of element of this structure.
 */
 interface Algebra<T> {

    /**
     * Wraps a raw string to [T] object. This method is designed for three purposes:
     *
     * 1. Mathematical constants (`e`, `pi`).
     * 1. Variables for expression-like contexts (`a`, `b`, `c`&hellip;).
     * 1. Literals (`{1, 2}`, (`(3; 4)`)).
     *
     * If algebra can't parse the string, then this method must throw [kotlin.IllegalStateException].
     *
     * @param value the raw string.
     * @return an object or `null` if symbol could not be bound to the context.
     */
     fun bindSymbolOrNull(value: String): T? = null

    /**
     * The same as [bindSymbolOrNull] but throws an error if symbol could not be bound
     */
     fun bindSymbol(value: String): T =
        bindSymbolOrNull(value) ?: error("Symbol '$value' is not supported in $this")

    /**
     * Dynamically dispatches a unary operation with the certain name.
     *
     * Implementations must fulfil the following requirements:
     *
     * 1. If operation is not defined in the structure, then the function throws [kotlin.IllegalStateException].
     * 1. Equivalence to [unaryOperation]: for any `a` and `b`, `unaryOperationFunction(a)(b) == unaryOperation(a, b)`.
     *
     * @param operation the name of operation.
     * @return an operation.
     */
     fun unaryOperationFunction(operation: String): (arg: T) -> T =
        error("Unary operation $operation not defined in $this")

    /**
     * Dynamically invokes a unary operation with the certain name.
     *
     * Implementations must fulfil the following requirements:
     *
     * 1. If operation is not defined in the structure, then the function throws [kotlin.IllegalStateException].
     * 1. Equivalence to [unaryOperationFunction]: i.e., for any `a` and `b`,
     * `unaryOperationFunction(a)(b) == unaryOperation(a, b)`.
     *
     * @param operation the name of operation.
     * @param arg the argument of operation.
     * @return a result of operation.
     */
     fun unaryOperation(operation: String, arg: T): T = unaryOperationFunction(operation)(arg)

    /**
     * Dynamically dispatches a binary operation with the certain name.
     *
     * Implementations must fulfil the following requirements:
     *
     * 1. If operation is not defined in the structure, then the function throws [kotlin.IllegalStateException].
     * 1. Equivalence to [binaryOperation]: for any `a`, `b`, and `c`,
     * `binaryOperationFunction(a)(b, c) == binaryOperation(a, b, c)`.
     *
     * @param operation the name of operation.
     * @return an operation.
     */
     fun binaryOperationFunction(operation: String): (left: T, right: T) -> T =
        error("Binary operation '$operation' not defined in $this")

    /**
     * Dynamically invokes a binary operation with the certain name.
     *
     * Implementations must fulfil the following requirements:
     *
     * 1. If operation is not defined in the structure, then the function throws [kotlin.IllegalStateException].
     * 1. Equivalence to [binaryOperationFunction]: for any `a`, `b`, and `c`,
     * `binaryOperationFunction(a)(b, c) == binaryOperation(a, b, c)`.
     *
     * @param operation the name of operation.
     * @param left the first argument of operation.
     * @param right the second argument of operation.
     * @return a result of operation.
     */
     fun binaryOperation(operation: String, left: T, right: T): T =
        binaryOperationFunction(operation)(left, right)

    /**
     * Export an algebra element, so it could be accessed even after algebra scope is closed.
     * This method must be used on algebras where data is stored externally or any local algebra state is used.
     * By default (if not overridden), exports the object itself.
     */
     fun export(arg: T): T = arg
}

 fun <T> Algebra<T>.bindSymbolOrNull(symbol: Symbol): T? = bindSymbolOrNull(symbol.identity)

 fun <T> Algebra<T>.bindSymbol(symbol: Symbol): T = bindSymbol(symbol.identity)

/**
 * Call a block with an [Algebra] as receiver.
 */
// TODO add contract when KT-32313 is fixed
 inline operator fun <A : Algebra<*>, R> A.invoke(block: A.() -> R): R = run(block)

